Skip to content

fix: harden git exec resolution in SourceLink metadata#53

Open
ANcpLua wants to merge 1 commit intomainfrom
dev/propose-fix-for-unqualified-git-exec
Open

fix: harden git exec resolution in SourceLink metadata#53
ANcpLua wants to merge 1 commit intomainfrom
dev/propose-fix-for-unqualified-git-exec

Conversation

@ANcpLua
Copy link
Copy Markdown
Owner

@ANcpLua ANcpLua commented Mar 22, 2026

Motivation

  • The _ANcpLuaComputeGitMetadata MSBuild target in src/Build/Enforcement/DeterminismAndSourceLink.props invoked git by name via Exec, enabling repository-local binary planting and a build-time command-hijack RCE vector.

Description

  • Add a _GitExe property that prefers an absolute $(GitExe) when provided and otherwise falls back to common absolute git install locations on Unix and Windows to ensure a trusted executable path.
  • Replace all bare git Exec invocations in the _ANcpLuaComputeGitMetadata target with calls to "$(_GitExe)" and guard the initial probe with Condition="'$(_GitExe)' != ''", preserving the non-git fallback behavior when no trusted git is found.
  • Keep existing stderr redirection and IgnoreExitCode behavior so metadata probing remains best-effort and existing warnings/fallbacks are unchanged.

Testing

  • Ran rg -n 'Exec Command="git ' against src/Build/Enforcement/DeterminismAndSourceLink.props and verified there are no remaining bare git Exec usages (success).
  • Inspected the updated file with sed/nl to confirm the _GitExe property and replacements were applied to all Exec lines in the target (success).
  • Attempted to run command -v dotnet and command -v msbuild to perform a build/test run but both tools are not available in this environment, so no dotnet/MSBuild test run could be executed (not executed).

Codex Task

Copilot AI review requested due to automatic review settings March 22, 2026 15:17
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the security of the build process by hardening the resolution of the git executable. It mitigates a potential RCE vulnerability by ensuring that git commands are executed from a trusted, absolute path rather than relying on environment variables that could be manipulated. This change improves the integrity and determinism of SourceLink metadata generation without altering the existing error handling or fallback mechanisms.

Highlights

  • Security Hardening: Addressed a potential RCE (Remote Code Execution) vulnerability by preventing repository-local binary planting when git is invoked via Exec in MSBuild targets.
  • Trusted Git Path Resolution: Introduced a new _GitExe MSBuild property that resolves the git executable to a trusted absolute path, prioritizing a user-provided $(GitExe) or falling back to common installation locations on Unix and Windows.
  • Updated Git Invocations: Modified all Exec invocations of git within the _ANcpLuaComputeGitMetadata target to use the securely resolved "$(_GitExe)" path, and added a condition to ensure execution only occurs if a trusted git path is found.
  • Preserved Behavior: Maintained existing stderr redirection and IgnoreExitCode behavior, ensuring that metadata probing remains best-effort and existing warnings or fallbacks are unchanged.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 22, 2026

Summary by CodeRabbit

Release Notes

  • Chores
    • Enhanced build system reliability for git integration by improving how the git executable is located and utilized during the build process.

Walkthrough

The change replaces implicit PATH-based git discovery with explicit, platform-aware git executable resolution via a new internal (_GitExe) property that checks an explicitly provided path and falls back to known Windows install locations.

Changes

Cohort / File(s) Summary
Git executable resolution
src/Build/Enforcement/DeterminismAndSourceLink.props
Added (_GitExe) property with fallback logic for locating git across Windows Program Files and LocalAppData paths. Updated all Exec invocations to invoke "$(_GitExe)" instead of PATH-based git discovery. Added guard condition on first Exec call that computes --show-toplevel.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Suggested labels

area:infra


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 error)

Check name Status Explanation Resolution
Title check ❌ Error The title uses the 'fix' prefix and describes the core security hardening of git executable resolution, but lacks a scope in parentheses as required by conventional commits format. Add scope in parentheses: 'fix(infra): harden git exec resolution in SourceLink metadata' or similar to match the enforced conventional commits format.
✅ Passed checks (5 passed)
Check name Status Explanation
Description check ✅ Passed The description comprehensively documents motivation, implementation, testing approach, and limitations, directly addressing all aspects of the changeset.
Otel Instrumentation Required ✅ Passed This PR adds no new injectable service classes and only modifies an MSBuild properties file, so the OpenTelemetry instrumentation requirement does not apply.
No Unbounded Mcp Responses ✅ Passed Repository lacks src/qyl.mcp/ directory and PR modifies only git executable hardening in DeterminismAndSourceLink.props—no MCP tool definitions added or modified.
Duckdb Backpressure On Write Paths ✅ Passed PR changes are confined to MSBuild .props file hardening git executable resolution; no DuckDB interactions or backpressure mechanisms involved.
Cancellationtoken Threading ✅ Passed PR modifies only MSBuild properties file (XML), not C# source code, so no new public async methods are introduced.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b02443af77

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +25 to +27
<_GitExe Condition="'$(_GitExe)' == '' and '$(GitExe)' != '' and $([System.IO.Path]::IsPathRooted('$(GitExe)'))">$(GitExe)</_GitExe>
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' != 'Windows_NT' and Exists('/usr/bin/git')">/usr/bin/git</_GitExe>
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' != 'Windows_NT' and Exists('/usr/local/bin/git')">/usr/local/bin/git</_GitExe>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve PATH-based git resolution

_GitExe is now only set from a short list of hard-coded absolute paths or an absolute GitExe override. For consumers whose Git is only discoverable through PATH—for example Homebrew/Nix installs on Unix or Scoop/portable installs on Windows—_GitExe stays empty, the rev-parse --show-toplevel call at line 35 is skipped, and this target then flips _ANcpLuaInGitRepo to false. The build still succeeds, but SourceLink and repository metadata are silently dropped for projects that are actually inside a git repo, which is a regression from the previous plain git ... invocations.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the MSBuild SourceLink/git metadata probing to avoid executing a repo-local planted git binary (command-hijack/RCE risk) by resolving git to a trusted absolute path before invoking it.

Changes:

  • Introduces a _GitExe property that selects an absolute git executable path (preferring an explicitly provided absolute $(GitExe)).
  • Replaces Exec Command="git ..." with Exec Command="\"$(_GitExe)\" ..." within _ANcpLuaComputeGitMetadata.
  • Guards the initial git-root probe to run only when a trusted git path was resolved.
Comments suppressed due to low confidence (4)

src/Build/Enforcement/DeterminismAndSourceLink.props:62

  • The later git Exec tasks are guarded by $(_ANcpLuaInGitRepo) but not by $(_GitExe). If _GitRoot (or _ANcpLuaInGitRepo) is ever pre-set from outside this props/target, these Execs can run with an empty $(_GitExe) (command becomes "" -C ...) and silently skip metadata. Consider adding and '$(_GitExe)' != '' to these Exec Conditions (or folding the $(_GitExe) check into the _ANcpLuaInGitRepo computation) so “no trusted git found” consistently disables all git Exec calls.
    <Exec Command="&quot;$(_GitExe)&quot; -C &quot;$(_GitRoot)&quot; config --get remote.origin.url$(_StderrRedirect)"
          ConsoleToMSBuild="true"
          IgnoreExitCode="true"
          StandardOutputImportance="Low"
          StandardErrorImportance="Low"
          Condition="'$(_ANcpLuaInGitRepo)' == 'true'">

src/Build/Enforcement/DeterminismAndSourceLink.props:71

  • This git Exec is conditioned on $(_ANcpLuaInGitRepo) but not on $(_GitExe). To keep the “no trusted git found” path consistent (and avoid executing an empty tool path if _GitRoot is ever set externally), consider adding and '$(_GitExe)' != '' here as well.
    <Exec Command="&quot;$(_GitExe)&quot; -C &quot;$(_GitRoot)&quot; symbolic-ref --quiet HEAD$(_StderrRedirect)"
          ConsoleToMSBuild="true"
          IgnoreExitCode="true"
          StandardOutputImportance="Low"
          StandardErrorImportance="Low"
          Condition="'$(_ANcpLuaInGitRepo)' == 'true'">

src/Build/Enforcement/DeterminismAndSourceLink.props:80

  • This git Exec is not guarded by $(_GitExe) != ''. If _ANcpLuaInGitRepo becomes true while _GitExe is empty (e.g., due to an externally supplied _GitRoot), the command will effectively be "" -C ... and silently fail. Add and '$(_GitExe)' != '' to the Condition to ensure all git invocations are disabled when no trusted git path is resolved.
    <Exec Command="&quot;$(_GitExe)&quot; -C &quot;$(_GitRoot)&quot; rev-parse --abbrev-ref HEAD$(_StderrRedirect)"
          ConsoleToMSBuild="true"
          IgnoreExitCode="true"
          StandardOutputImportance="Low"
          StandardErrorImportance="Low"
          Condition="'$(_ANcpLuaInGitRepo)' == 'true' and '$(_GitBranchRefRaw)' == ''">

src/Build/Enforcement/DeterminismAndSourceLink.props:89

  • This git Exec is conditioned on $(_ANcpLuaInGitRepo) but not on $(_GitExe). For consistency with the initial probe (and to avoid attempting to execute an empty tool path), add and '$(_GitExe)' != '' to this Condition (or ensure _ANcpLuaInGitRepo can’t be true when _GitExe is empty).
    <Exec Command="&quot;$(_GitExe)&quot; -C &quot;$(_GitRoot)&quot; rev-parse HEAD$(_StderrRedirect)"
          ConsoleToMSBuild="true"
          IgnoreExitCode="true"
          StandardOutputImportance="Low"
          StandardErrorImportance="Low"
          Condition="'$(_ANcpLuaInGitRepo)' == 'true'">

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +28 to +31
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' == 'Windows_NT' and Exists('$(ProgramW6432)\Git\cmd\git.exe')">$(ProgramW6432)\Git\cmd\git.exe</_GitExe>
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' == 'Windows_NT' and Exists('$(ProgramFiles)\Git\cmd\git.exe')">$(ProgramFiles)\Git\cmd\git.exe</_GitExe>
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' == 'Windows_NT' and Exists('$(ProgramFiles)\Git\bin\git.exe')">$(ProgramFiles)\Git\bin\git.exe</_GitExe>
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' == 'Windows_NT' and Exists('$(LocalAppData)\Programs\Git\cmd\git.exe')">$(LocalAppData)\Programs\Git\cmd\git.exe</_GitExe>
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Windows, properties like $(ProgramW6432) can be empty (e.g., 32-bit environments). In that case Exists('$(ProgramW6432)\Git\cmd\git.exe') becomes Exists('\Git\cmd\git.exe'), which can unexpectedly match a drive-root path and undermines the intent of selecting a well-known install location. Add guards like '$(ProgramW6432)' != '' (and similarly for other base dirs) before the Exists(...) checks; also consider adding $(ProgramFiles(x86))\Git\cmd\git.exe as another common Git-for-Windows install location.

Suggested change
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' == 'Windows_NT' and Exists('$(ProgramW6432)\Git\cmd\git.exe')">$(ProgramW6432)\Git\cmd\git.exe</_GitExe>
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' == 'Windows_NT' and Exists('$(ProgramFiles)\Git\cmd\git.exe')">$(ProgramFiles)\Git\cmd\git.exe</_GitExe>
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' == 'Windows_NT' and Exists('$(ProgramFiles)\Git\bin\git.exe')">$(ProgramFiles)\Git\bin\git.exe</_GitExe>
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' == 'Windows_NT' and Exists('$(LocalAppData)\Programs\Git\cmd\git.exe')">$(LocalAppData)\Programs\Git\cmd\git.exe</_GitExe>
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' == 'Windows_NT' and '$(ProgramW6432)' != '' and Exists('$(ProgramW6432)\Git\cmd\git.exe')">$(ProgramW6432)\Git\cmd\git.exe</_GitExe>
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' == 'Windows_NT' and '$(ProgramFiles(x86))' != '' and Exists('$(ProgramFiles(x86))\Git\cmd\git.exe')">$(ProgramFiles(x86))\Git\cmd\git.exe</_GitExe>
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' == 'Windows_NT' and '$(ProgramFiles)' != '' and Exists('$(ProgramFiles)\Git\cmd\git.exe')">$(ProgramFiles)\Git\cmd\git.exe</_GitExe>
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' == 'Windows_NT' and '$(ProgramFiles)' != '' and Exists('$(ProgramFiles)\Git\bin\git.exe')">$(ProgramFiles)\Git\bin\git.exe</_GitExe>
<_GitExe Condition="'$(_GitExe)' == '' and '$(OS)' == 'Windows_NT' and '$(LocalAppData)' != '' and Exists('$(LocalAppData)\Programs\Git\cmd\git.exe')">$(LocalAppData)\Programs\Git\cmd\git.exe</_GitExe>

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request hardens the build process against potential command-hijacking vulnerabilities by resolving the git executable to a trusted, absolute path instead of relying on the system's PATH. The approach of defining a _GitExe property with a fallback mechanism to common installation locations is solid. My review includes suggestions to further improve the robustness of the implementation by ensuring all git command executions are skipped if a trusted git executable cannot be found.

</PropertyGroup>

<Exec Command="git -C &quot;$(_GitRoot)&quot; config --get remote.origin.url$(_StderrRedirect)"
<Exec Command="&quot;$(_GitExe)&quot; -C &quot;$(_GitRoot)&quot; config --get remote.origin.url$(_StderrRedirect)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For consistency with the first Exec task and to improve robustness, it's advisable to add a condition here to ensure this task only runs if _GitExe has been successfully resolved. Without the condition, if _GitExe is empty, MSBuild will attempt to execute an empty command, which will fail. Although IgnoreExitCode="true" prevents a build failure, explicitly skipping the task is a cleaner approach.

    <Exec Command="&quot;$(_GitExe)&quot; -C &quot;$(_GitRoot)&quot; config --get remote.origin.url$(_StderrRedirect)"
          Condition="'$(_GitExe)' != ''"

</Exec>

<Exec Command="git -C &quot;$(_GitRoot)&quot; symbolic-ref --quiet HEAD$(_StderrRedirect)"
<Exec Command="&quot;$(_GitExe)&quot; -C &quot;$(_GitRoot)&quot; symbolic-ref --quiet HEAD$(_StderrRedirect)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to my other comments, adding a condition to this Exec task will make the logic more robust by preventing an attempt to run an empty command if a trusted git executable isn't found.

    <Exec Command="&quot;$(_GitExe)&quot; -C &quot;$(_GitRoot)&quot; symbolic-ref --quiet HEAD$(_StderrRedirect)"
          Condition="'$(_GitExe)' != ''"

</Exec>

<Exec Command="git -C &quot;$(_GitRoot)&quot; rev-parse --abbrev-ref HEAD$(_StderrRedirect)"
<Exec Command="&quot;$(_GitExe)&quot; -C &quot;$(_GitRoot)&quot; rev-parse --abbrev-ref HEAD$(_StderrRedirect)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To maintain consistency and prevent errors from attempting to run an empty command, please add a condition to this Exec task to ensure it only runs when _GitExe is defined.

    <Exec Command="&quot;$(_GitExe)&quot; -C &quot;$(_GitRoot)&quot; rev-parse --abbrev-ref HEAD$(_StderrRedirect)"
          Condition="'$(_GitExe)' != ''"

</Exec>

<Exec Command="git -C &quot;$(_GitRoot)&quot; rev-parse HEAD$(_StderrRedirect)"
<Exec Command="&quot;$(_GitExe)&quot; -C &quot;$(_GitRoot)&quot; rev-parse HEAD$(_StderrRedirect)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

As with the other git commands in this target, this Exec task should also be conditional on _GitExe being set. This will prevent it from trying to execute an empty command if git is not found in one of the trusted locations.

    <Exec Command="&quot;$(_GitExe)&quot; -C &quot;$(_GitRoot)&quot; rev-parse HEAD$(_StderrRedirect)"
          Condition="'$(_GitExe)' != ''"

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Build/Enforcement/DeterminismAndSourceLink.props`:
- Line 25: The condition that sets _GitExe from $(GitExe) should require a truly
fully-qualified path and verify the file exists; replace the Path.IsPathRooted
check with Path.IsPathFullyQualified when available (or a regex check such as
^[A-Za-z]:\\|^\\\\ for .NET Framework) and add an Exists check so the assignment
only occurs if the path is fully qualified and File.Exists('$(GitExe)') is true;
update the conditional that references _GitExe and GitExe to use these stricter
validations to avoid accepting drive-relative paths like C:git.exe or stale
overrides.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ccd78645-0dfb-46b0-b608-6706130a0f42

📥 Commits

Reviewing files that changed from the base of the PR and between 8d35cc8 and b02443a.

📒 Files selected for processing (1)
  • src/Build/Enforcement/DeterminismAndSourceLink.props
📜 Review details
⏰ Context from checks skipped due to timeout of 120000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: test (ubuntu-latest)
  • GitHub Check: test (windows-latest)
  • GitHub Check: test (macos-latest)
  • GitHub Check: Agent
🧰 Additional context used
📓 Path-based instructions (1)
**/*.props

⚙️ CodeRabbit configuration file

MSBuild property files (Directory.Build.props, Directory.Packages.props, Version.props). Review for: Central Package Management correctness, version consistency, and that new packages are added with explicit version pins. Flag transitive dependency promotions that aren't justified. Verify TFM targeting is correct (.NET 10).

Files:

  • src/Build/Enforcement/DeterminismAndSourceLink.props

<_StderrRedirect Condition="'$(OS)' != 'Windows_NT'"> 2&gt;/dev/null</_StderrRedirect>

<!-- Resolve git to a trusted absolute path so repo-local binary planting cannot hijack Exec. -->
<_GitExe Condition="'$(_GitExe)' == '' and '$(GitExe)' != '' and $([System.IO.Path]::IsPathRooted('$(GitExe)'))">$(GitExe)</_GitExe>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In MSBuild property functions on the supported SDK/Visual Studio versions, does System.IO.Path.IsPathRooted("C:git.exe")return true, and isSystem.IO.Path.IsPathFullyQualified available for enforcing a truly absolute path?

💡 Result:

  • Yes. On Windows, System.IO.Path.IsPathRooted("C:git.exe") returns true because IsPathRooted treats “drive letter + colon” as “rooted” (even though it’s still drive-relative, not truly absolute). The .NET docs for IsPathFullyQualified show the same behavior with "C:Documents": Rooted = True, Fully qualified = False. [1], [2]

  • System.IO.Path.IsPathFullyQualified(...) exists on modern .NET / .NET Standard (not .NET Framework) (the API is documented as coming from netstandard.dll / System.Runtime and is intended to distinguish "C:a" vs "C:\\a"). [1]
    MSBuild property functions can call public static .NET methods, but only those available in the runtime hosting MSBuild. [3]

  • Practical upshot for “supported SDK/VS versions”:

    • If you build with the .NET SDK MSBuild (for example via dotnet msbuild / dotnet build), you can use Path.IsPathFullyQualified to enforce a truly absolute path. [4]
    • If you build with MSBuild running on .NET Framework (common for Visual Studio’s in-proc/toolset MSBuild), Path.IsPathFullyQualified is not available, so calling it in a property function will fail at evaluation time (you’ll need an alternate check, e.g., pattern-testing for ^[A-Za-z]:[\\/]|^\\\\). [1], [3]

Sources: [1] [2] [3] [4]


Require $(GitExe) to be fully qualified and to exist before trusting it.

Path.IsPathRooted accepts drive-relative Windows paths like C:git.exe (rooted, not fully qualified), which can resolve relative to the current directory and weakens this hardening. The check also short-circuits fallback probes even when the file doesn't exist, silently disabling git metadata on a stale override.

Use Path.IsPathFullyQualified on .NET SDK MSBuild (e.g., dotnet build), or implement an equivalent check for .NET Framework MSBuild. Always pair with Exists validation.

Suggested change
-    <_GitExe Condition="'$(_GitExe)' == '' and '$(GitExe)' != '' and $([System.IO.Path]::IsPathRooted('$(GitExe)'))">$(GitExe)</_GitExe>
+    <_GitExe Condition="'$(_GitExe)' == '' and '$(GitExe)' != '' and $([System.IO.Path]::IsPathFullyQualified('$(GitExe)')) and Exists('$(GitExe)')">$(GitExe)</_GitExe>

For .NET Framework MSBuild, replace IsPathFullyQualified with a pattern check or equivalent validation ensuring the path is truly absolute (e.g., ^[A-Za-z]:\\|^\\\\).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/Build/Enforcement/DeterminismAndSourceLink.props` at line 25, The
condition that sets _GitExe from $(GitExe) should require a truly
fully-qualified path and verify the file exists; replace the Path.IsPathRooted
check with Path.IsPathFullyQualified when available (or a regex check such as
^[A-Za-z]:\\|^\\\\ for .NET Framework) and add an Exists check so the assignment
only occurs if the path is fully qualified and File.Exists('$(GitExe)') is true;
update the conditional that references _GitExe and GitExe to use these stricter
validations to avoid accepting drive-relative paths like C:git.exe or stale
overrides.

@ANcpLua
Copy link
Copy Markdown
Owner Author

ANcpLua commented Mar 24, 2026

@claude review the suggestions and implemented fixes merge when all done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants